import json
import time
from typing import List
from fastapi import Request
from tortoise.expressions import Q
from tortoise.transactions import in_transaction
from hypertext import PagingResult
from exception import AppException
from common.utils.urls import UrlUtil
from common.utils.times import TimeUtil
from common.utils.tools import ToolsUtil
from common.utils.config import ConfigUtil
from common.enums.apply import VideoEnum
from common.enums.wallet import WalletEnum
from common.models.users import UserModel
from common.models.users import UserWalletModel
from common.models.video import VideoSquareModel
from common.models.video import VideoCollectModel
from common.models.video import VideoRecordsModel
from common.chain.video_server import VideoZhipuServer
from common.chain.video_server import VideoOpenAiHkServer
from common.service.subscribe_logic import SubscribeLogic
from plugins.censor.driver import CensorDriver
from apps.api.schemas.apply import video_schema as schema


class VideoBasicService:

    @classmethod
    async def config(cls, user_id: int, engine: str) -> schema.VideoConfigVo:
        """
        视频配置参数

        Returns:
           List[schema.MusicConfigVo]

        Author:
           zero
        """
        options = []
        configs = {
            "status": False,
            "points": 0,
            "surplus_num": 0,
            "free_status": False
        }

        # 订阅特权
        member = await SubscribeLogic.get_surplus_free(user_id=user_id, scene="video", mark=engine)
        if member:
            configs["surplus_num"] = member.get("surplus_num")
            configs["free_status"] = member.get("free_status")

        # Luma配置
        luma = await ConfigUtil.get("video", "luma") or {}
        if luma.get("status"):
            name = luma.get("name") or "Luma"
            options.append({"type": "luma", "name": name})

        # 智谱配置
        zhipu = await ConfigUtil.get("video", "zhipu") or {}
        if zhipu.get("status"):
            name = zhipu.get("name") or "CogVideoX"
            options.append({"type": "zhipu", "name": name})

        # 场景处理
        if engine == VideoEnum.ENGINE_LUMA:
            configs["status"] = bool(luma.get("status"))
            configs["points"] = int(luma.get("points", 0))
        elif engine == VideoEnum.ENGINE_ZHIPU:
            configs["status"] = bool(zhipu.get("status"))
            configs["points"] = int(zhipu.get("points", 0))

        return schema.VideoConfigVo(
            options=options,
            configs=configs
        )

    @classmethod
    async def record(cls, user_id: int, params: schema.VideoRecordSearchIn) -> PagingResult[schema.VideoRecordsVo]:
        # 查询条件
        where = [Q(engine=params.engine), Q(user_id=user_id), Q(is_delete=0)]
        if params.status and int(params.status) > 0:
            where.append(Q(status=int(params.status)))

        # 查询数据
        _model = VideoRecordsModel.filter(*where).order_by(str("-id"))
        _pager = await VideoRecordsModel.paginate(
            model=_model,
            page_no=params.page_no,
            page_size=params.page_size
        )

        # 广场查询
        record_ids = [item["id"] for item in _pager.lists]
        squares = await VideoSquareModel.querySquareDictsByRecordId(record_ids)

        # 处理列表
        _lists = []
        for item in _pager.lists:
            # 广场状态: [-1=未分享,-2=被隐藏,0=待审核,1=通过,2=拒绝]
            square = squares.get(item["id"], {})
            share_status = square.get("audit_status", -1)
            if share_status == 1 and square.get("is_enable") == 0:
                share_status = - 2

            _lists.append(schema.VideoRecordsVo(
                engine=item["engine"],
                code=item["code"],
                type=item["type"],
                scale=item["scale"],
                prompt=item["prompt"],
                image_url=await UrlUtil.to_absolute_url(item["image_url"]),
                video_url=await UrlUtil.to_absolute_url(item["video_url"]),
                use_points=item["use_points"],
                status=item["status"],
                is_free=item["is_free"],
                share_status=share_status,
                share_result=square.get("audit_result") or "",
                fail_reason=item["fail_reason"] or "",
                create_time=item["create_time"]
            ))

        _pager.lists = _lists
        return _pager

    @classmethod
    async def detection(cls, user_id: int, ids: List[str]) -> List[schema.VideoRecordsVo]:
        """
        视频记录检测

        Args:
            user_id (int): 用户ID
            ids (List[str]): 视频记录编号列表

        Returns:
            List[schema.VideoRecordsVo]

        Author:
            zero
        """
        if len(ids) <= 0:
            return []

        lists = await (VideoRecordsModel
                       .filter(code__in=ids)
                       .filter(user_id=user_id)
                       .filter(is_delete=0)
                       .limit(110)
                       .all())

        # 广场查询
        record_ids = [item.id for item in lists]
        squares = await VideoSquareModel.querySquareDictsByRecordId(record_ids)

        # 处理列表
        _lists = []
        for item in lists:
            # 广场状态: [-1=未分享,-2=被隐藏,0=待审核,1=通过,2=拒绝]
            square = squares.get(item.id, {})
            share_status = square.get("audit_status", -1)
            if share_status == 1 and square.get("is_enable") == 0:
                share_status = - 2

            _lists.append(schema.VideoRecordsVo(
                engine=item.engine,
                code=item.code,
                type=item.type,
                scale=item.scale,
                prompt=item.prompt,
                image_url=await UrlUtil.to_absolute_url(item.image_url),
                video_url=await UrlUtil.to_absolute_url(item.video_url),
                use_points=item.use_points,
                status=item.status,
                is_free=item.is_free,
                share_status=share_status,
                share_result=square.get("audit_result") or "",
                fail_reason=item.fail_reason or "",
                create_time=TimeUtil.timestamp_to_date(item.create_time)
            ))

        return _lists

    @classmethod
    async def delete(cls, user_id: int, code: str) -> None:
        """
        视频记录删除

        Args:
            user_id (int): 用户ID
            code (str): 视频记录编号

        Author:
            zero
        """
        record = await (VideoRecordsModel
                        .filter(code=code)
                        .filter(user_id=user_id)
                        .filter(is_delete=0)
                        .get())

        record.is_delete = 1
        record.delete_time = int(time.time())
        await record.save()
    
    @classmethod
    async def square(cls, user_id: int, code: str) -> None:
        """
        音乐广场公开

        Args:
            user_id (int): 用户ID
            code (str): 记录编号

        Author:
            zero
        """
        record = await VideoRecordsModel.filter(user_id=user_id, code=code).first()
        if record is None:
            raise AppException("视频记录不存在")

        square = await VideoSquareModel.filter(record_id=record.id).first()
        if record.is_delete:
            if square is None or square.is_delete:
                raise AppException("视频记录不存在")

        if square is None:
            config = await ConfigUtil.get("square", "video", {})
            audit_type = int(config.get("audit_type", 0))
            await VideoSquareModel.create(
                source=2,
                cate_id=0,
                operate_id=user_id,
                record_id=record.id,
                video_code=record.code,
                video_url=record.video_url,
                prompt=record.prompt,
                public_sum=1,
                audit_result="",
                audit_status=0 if audit_type == 0 else 1,
                is_enable=1,
                create_time=int(time.time()),
                update_time=int(time.time())
            )
        else:
            if square.is_delete:
                await VideoSquareModel.filter(id=square.id).update(
                    public_sum=square.public_sum + 1,
                    is_delete=0,
                    delete_time=0
                )
            else:
                await VideoSquareModel.filter(id=square.id).update(
                    is_delete=1,
                    delete_time=int(time.time())
                )

    @classmethod
    async def collect(cls, user_id: int, code: str) -> None:
        """
        视频记录收藏

        Args:
            user_id (int): 用户ID
            code (str): 记录编号

        Author:
            zero
        """
        record = await VideoRecordsModel.filter(user_id=user_id, code=code).first()
        if record is None:
            raise AppException("视频记录不存在")

        collect = await VideoCollectModel.filter(user_id=user_id, record_id=record.id).first()
        square = await VideoSquareModel.filter(record_id=record.id).first()
        if square is None:
            if collect is None or collect.is_delete:
                raise AppException("视频记录不存在")

        if collect is None:
            await VideoCollectModel.create(
                user_id=user_id,
                record_id=int(record.id),
                total_num=int(1),
                create_time=int(time.time()),
                update_time=int(time.time())
            )
        else:
            if collect.is_delete:
                await VideoCollectModel.filter(id=collect.id).update(
                    total_num=collect.total_num + 1,
                    is_delete=0,
                    delete_time=0
                )
            else:
                await VideoCollectModel.filter(id=collect.id).update(
                    is_delete=1,
                    delete_time=int(time.time())
                )


class VideoGenService:
    def __init__(self, request: Request, user_id: int,  params: schema.VideoGenerateIn):
        self.request = request
        self.user_id = user_id
        self.params = params
        self.user = None
        self.channel: str = ""
        self.model: str = ""
        self.points: int = 0
        self.vip_free: bool = False
        self.other_params = {}

        self.type = VideoEnum.TYPE_TEXT_TO_VIDEO
        if self.params.image_url:
            self.type = VideoEnum.TYPE_IMAGE_TO_VIDEO

    async def generate(self):
        # 验证参数
        await self._initialize()

        # 验证费用
        await self._check_charging()

        # 违规审查
        review = CensorDriver.REVIEW_VIDEO
        censor = await CensorDriver.text(user_id=self.user_id, text=self.params.prompt, scene=review)
        if censor is not None:
            raise AppException(f"描述词违规: {censor}")

        # 发起请求
        try:
            if self.params.engine == VideoEnum.ENGINE_LUMA:
                params = {
                    "model": self.model,
                    "prompt": self.params.prompt,
                    "image_url": self.params.image_url,
                    "scale": self.params.scale,
                    "other_params": {
                        "image_end_url": str(self.params.other_params.get("image_end_url", "")),
                        "expand_prompt": bool(self.params.other_params.get("expand_prompt", False)),
                        "loop": bool(self.params.other_params.get("loop", False))
                    }
                }

                self.other_params = params["other_params"]
                if self.other_params["image_end_url"]:
                    self.other_params["image_end_url"] = UrlUtil.to_relative_url(self.other_params["image_end_url"])

                video_server = VideoOpenAiHkServer()
                return await video_server.submit(params)
            elif self.params.engine == VideoEnum.ZHIPU:
                params = {
                    "model": self.model,
                    "prompt": self.params.prompt,
                    "image_url": self.params.image_url,
                    "scale": self.params.scale,
                    "other_params": {
                        "fps": int(self.params.other_params.get("fps", 30)),
                        "quality": str(self.params.other_params.get("quality", "speed")),
                        "with_audio": bool(self.params.other_params.get("with_audio", False))
                    }
                }
                self.other_params = params.get("other_params")
                response = await (VideoZhipuServer()).submit(params)
            else:
                raise Exception("Engine not found")

            await self._save_record(response.get("id"))
        except Exception as e:
            raise AppException(str(e))

    async def _initialize(self):
        # 获取用户
        self.user = await UserModel.filter(id=self.user_id).first()
        if not self.user:
            raise AppException("用户信息错误")

    async def _check_charging(self):
        """ 验证费用 """
        # 验证通道状态
        config = await ConfigUtil.get("video", self.params.engine) or {}
        if not config.get("status"):
            AppException("当前视频渠道已关闭")

        # 获取渠道数据
        self.points = int(config.get("points", "0"))
        self.model = config.get("model", "")

        # 获取订阅会员
        member = await SubscribeLogic.get_surplus_free(user_id=self.user_id, scene="video", mark=self.params.engine)
        if member:
            self.vip_free = member.get("free_status")

        # 验证账户余额
        if not self.vip_free and self.user.points < self.points:
            raise AppException("账户余额不足,请充值!")

    async def _save_record(self, task_id: str):
        """
        保存记录

        Args:
            task_id (str): 任务ID

        Author:
            zero
        """
        async with in_transaction("mysql"):
            if self.vip_free:
                self.points = 0
            else:
                # 用户扣费
                if self.points > 0:
                    self.user.points = self.user.points - self.points
                    await self.user.save()
                # 账户明细
                if self.points > 0:
                    await UserWalletModel.dec(
                        scene=WalletEnum.POINTS,
                        user_id=self.user_id,
                        change_type=WalletEnum.POINTS_DEC_DRAWS,
                        change_amount=self.points,
                        project="AI视频"
                    )

            # 写入记录
            code = ToolsUtil.make_md5_str(ToolsUtil.make_uuid() + task_id)
            await VideoRecordsModel.create(
                code=code,
                user_id=self.user_id,
                task_id=task_id,
                type=self.type,
                engine=self.params.engine,
                channel=self.channel,
                model=self.model,
                prompt=self.params.prompt,
                scale=self.params.scale,
                image_url=self.params.image_url,
                other_params=json.dumps(self.other_params, ensure_ascii=False),
                use_points=self.points,
                status=VideoEnum.STATUS_WAIT,
                ip=self.request.client.host,
                create_time=int(time.time()),
                update_time=int(time.time())
            )
